Skip to content

graph, store: Reject grafts below the base's earliest block#6610

Open
incrypto32 wants to merge 1 commit into
masterfrom
fix/graft-validate-earliest-block
Open

graph, store: Reject grafts below the base's earliest block#6610
incrypto32 wants to merge 1 commit into
masterfrom
fix/graft-validate-earliest-block

Conversation

@incrypto32
Copy link
Copy Markdown
Member

Graft::validate checks that the graft block is below base.latest_block, above the reorg threshold, and that the base is healthy. It does not check that the graft block is at or above base.earliest_block_number.

When the base has been pruned past the graft block (or its start block is above it), the copy step silently leaves the destination missing all pre-graft mutable entity versions. Dynamic data source registrations in data_sources$ survive (they are never pruned), so the destination keeps listening to factory-created contracts whose backing entities are gone, producing unexpected null in handler errors on every event.

This adds the missing check, mirroring the equivalent guard added for graphman copy in #6384.

Closes #6581
Closes #6607

Mirrors the guard added for `graphman copy` in #6384. Without this,
grafting at a block below `base.earliest_block_number` silently produces
a destination missing all pre-graft mutable entity versions, and (when
the base has dynamic data sources) handlers fail with
`unexpected null in handler` because `data_sources$` registrations
survive pruning while the entities they reference do not.
@0x19dG87
Copy link
Copy Markdown

0x19dG87 commented Jun 1, 2026

@incrypto32 Why it should reject? Should not subgraph that uses graft follow graft structure and data and therefore just copy graft data and start from the latest graft block?

@incrypto32
Copy link
Copy Markdown
Member Author

@0x19dG87 My reasoning was that graft.block today means "the block the new deployment starts at, with all entity state up to and including that block inherited from the base."

If graft.block < earliest_block_number, the copy step matches nothing for mutable entities, since pruning deleted them. The destination starts without the inherited state the manifest implies it has, and mappings hit null for entities they expect (see #6607).

Also there is implications for POI: two indexers grafting the same deployment would diverge depending on whether they grafted before or after the base was pruned. The one with the full copy reads real pre-graft state into handlers; the one with the empty copy reads null and writes different entities based on how mappings handle it.

Happy to discuss this further if there's uncertainities. Yet to be discussed internally and waiting for a review too from the team.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants